// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cast/test/utility/video_utility.h"

#include <math.h>
#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <cstdio>

#include "base/rand_util.h"
#include "third_party/libyuv/include/libyuv/compare.h"
#include "ui/gfx/geometry/size.h"

namespace media {
namespace cast {

    double I420PSNR(const scoped_refptr<media::VideoFrame>& frame1,
        const scoped_refptr<media::VideoFrame>& frame2)
    {
        if (frame1->visible_rect().width() != frame2->visible_rect().width() || frame1->visible_rect().height() != frame2->visible_rect().height())
            return -1;

        return libyuv::I420Psnr(frame1->visible_data(VideoFrame::kYPlane),
            frame1->stride(VideoFrame::kYPlane),
            frame1->visible_data(VideoFrame::kUPlane),
            frame1->stride(VideoFrame::kUPlane),
            frame1->visible_data(VideoFrame::kVPlane),
            frame1->stride(VideoFrame::kVPlane),
            frame2->visible_data(VideoFrame::kYPlane),
            frame2->stride(VideoFrame::kYPlane),
            frame2->visible_data(VideoFrame::kUPlane),
            frame2->stride(VideoFrame::kUPlane),
            frame2->visible_data(VideoFrame::kVPlane),
            frame2->stride(VideoFrame::kVPlane),
            frame1->visible_rect().width(),
            frame1->visible_rect().height());
    }

    double I420SSIM(const scoped_refptr<media::VideoFrame>& frame1,
        const scoped_refptr<media::VideoFrame>& frame2)
    {
        if (frame1->visible_rect().width() != frame2->visible_rect().width() || frame1->visible_rect().height() != frame2->visible_rect().height())
            return -1;

        return libyuv::I420Ssim(frame1->visible_data(VideoFrame::kYPlane),
            frame1->stride(VideoFrame::kYPlane),
            frame1->visible_data(VideoFrame::kUPlane),
            frame1->stride(VideoFrame::kUPlane),
            frame1->visible_data(VideoFrame::kVPlane),
            frame1->stride(VideoFrame::kVPlane),
            frame2->visible_data(VideoFrame::kYPlane),
            frame2->stride(VideoFrame::kYPlane),
            frame2->visible_data(VideoFrame::kUPlane),
            frame2->stride(VideoFrame::kUPlane),
            frame2->visible_data(VideoFrame::kVPlane),
            frame2->stride(VideoFrame::kVPlane),
            frame1->visible_rect().width(),
            frame1->visible_rect().height());
    }

    void PopulateVideoFrame(VideoFrame* frame, int start_value)
    {
        const gfx::Size frame_size = frame->coded_size();
        const int stripe_size = std::max(32, std::min(frame_size.width(), frame_size.height()) / 8) & -2;

        // Set Y.
        const int height = frame_size.height();
        const int stride_y = frame->stride(VideoFrame::kYPlane);
        uint8_t* y_plane = frame->data(VideoFrame::kYPlane);
        for (int j = 0; j < height; ++j) {
            const int stripe_j = (j / stripe_size) * stripe_size;
            for (int i = 0; i < stride_y; ++i) {
                const int stripe_i = (i / stripe_size) * stripe_size;
                *y_plane = static_cast<uint8_t>(start_value + stripe_i + stripe_j);
                ++y_plane;
            }
        }

        const int half_height = (height + 1) / 2;
        if (frame->format() == PIXEL_FORMAT_NV12) {
            const int stride_uv = frame->stride(VideoFrame::kUVPlane);
            uint8_t* uv_plane = frame->data(VideoFrame::kUVPlane);

            // Set U and V.
            for (int j = 0; j < half_height; ++j) {
                const int stripe_j = (j / stripe_size) * stripe_size;
                for (int i = 0; i < stride_uv; i += 2) {
                    const int stripe_i = (i / stripe_size) * stripe_size;
                    *uv_plane = *(uv_plane + 1) = static_cast<uint8_t>(start_value + stripe_i + stripe_j);
                    uv_plane += 2;
                }
            }
        } else {
            DCHECK(frame->format() == PIXEL_FORMAT_I420 || frame->format() == PIXEL_FORMAT_YV12);
            const int stride_u = frame->stride(VideoFrame::kUPlane);
            const int stride_v = frame->stride(VideoFrame::kVPlane);
            uint8_t* u_plane = frame->data(VideoFrame::kUPlane);
            uint8_t* v_plane = frame->data(VideoFrame::kVPlane);

            // Set U.
            for (int j = 0; j < half_height; ++j) {
                const int stripe_j = (j / stripe_size) * stripe_size;
                for (int i = 0; i < stride_u; ++i) {
                    const int stripe_i = (i / stripe_size) * stripe_size;
                    *u_plane = static_cast<uint8_t>(start_value + stripe_i + stripe_j);
                    ++u_plane;
                }
            }

            // Set V.
            for (int j = 0; j < half_height; ++j) {
                const int stripe_j = (j / stripe_size) * stripe_size;
                for (int i = 0; i < stride_v; ++i) {
                    const int stripe_i = (i / stripe_size) * stripe_size;
                    *v_plane = static_cast<uint8_t>(start_value + stripe_i + stripe_j);
                    ++v_plane;
                }
            }
        }
    }

    void PopulateVideoFrameWithNoise(VideoFrame* frame)
    {
        const int height = frame->coded_size().height();
        const int stride_y = frame->stride(VideoFrame::kYPlane);
        const int stride_u = frame->stride(VideoFrame::kUPlane);
        const int stride_v = frame->stride(VideoFrame::kVPlane);
        const int half_height = (height + 1) / 2;
        uint8_t* const y_plane = frame->data(VideoFrame::kYPlane);
        uint8_t* const u_plane = frame->data(VideoFrame::kUPlane);
        uint8_t* const v_plane = frame->data(VideoFrame::kVPlane);

        base::RandBytes(y_plane, height * stride_y);
        base::RandBytes(u_plane, half_height * stride_u);
        base::RandBytes(v_plane, half_height * stride_v);
    }

    bool PopulateVideoFrameFromFile(VideoFrame* frame, FILE* video_file)
    {
        const int width = frame->coded_size().width();
        const int height = frame->coded_size().height();
        const int half_width = (width + 1) / 2;
        const int half_height = (height + 1) / 2;
        const size_t frame_size = width * height + 2 * half_width * half_height;
        uint8_t* const y_plane = frame->data(VideoFrame::kYPlane);
        uint8_t* const u_plane = frame->data(VideoFrame::kUPlane);
        uint8_t* const v_plane = frame->data(VideoFrame::kVPlane);

        uint8_t* const raw_data = new uint8_t[frame_size];
        const size_t count = fread(raw_data, 1, frame_size, video_file);
        if (count != frame_size)
            return false;

        memcpy(y_plane, raw_data, width * height);
        memcpy(u_plane, raw_data + width * height, half_width * half_height);
        memcpy(v_plane,
            raw_data + width * height + half_width * half_height,
            half_width * half_height);
        delete[] raw_data;
        return true;
    }

} // namespace cast
} // namespace media
